package com.panopset;

import static com.panopset.Util.ARIAL_PLAIN_10;
import static com.panopset.Util.ARIAL_PLAIN_12;
import static com.panopset.Util.PERIOD;
import static com.panopset.Util.SINGLE_SPACE;
import static com.panopset.Util.VERSION_SERIAL_NUMBER;
import static com.panopset.Util.dump;
import static com.panopset.Util.log;
import static com.panopset.Util.parentFrame;
import static com.panopset.Util.x;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.math.BigDecimal;
import java.util.Properties;

import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;

import com.panopset.centralen.resource.Favicon;
import com.panopset.centralen.resource.PersistentContainer;
import com.panopset.centralen.util.MathUtil;
import com.panopset.gui.FlexFrame;
import com.panopset.gui.Osgw;
import com.panopset.gui.PopupBox;
import com.panopset.gui.TButton;
import com.panopset.gui.TPanel;
import com.panopset.gui.TypicalDialog;
import com.panopset.io.DocumentManager;

/**
 * A basic Java Swing application foundation.
 *
 * @author Karl Dinwiddie
 */
public abstract class AbstractApplication implements ApplicationInterface {

    /**
     * Ninety percent memory used.
     */
    private static final double NINETY = 90.0D;

    /**
     * Main panel.
     */
    private JPanel mainPanel;

    /**
     * Flex frame.
     */
    private FlexFrame flexFrame;

    /**
     * Menu bar.
     */
    private JMenuBar pmenuBar;

    /**
     * Application name.
     */
    private String applicationName;

    /**
     * App refrence.
     */
    private static AbstractApplication app;

    /**
     * Document manager.
     */
    private DocumentManager documentManager;

    /**
     * Constructor.
     */
    public AbstractApplication() {
        setApp(this);
    }

    /**
     * @return true if static reference to the application exists.
     */
    public static boolean exists() {
        return app != null;
    }

    /**
     * @return static reference to application.
     */
    public static AbstractApplication getApp() {
        return app;
    }

    /**
     * Set static reference to application.
     *
     * @param application
     *            Panopset abstract application.
     */
    private static void setApp(final AbstractApplication application) {
        AbstractApplication.app = application;
    }

    /**
     * Add a persistent container.
     *
     * @param container
     *            Persistent container to add.
     */
    public static void add(final PersistentContainer container) {
        if (exists()) {
            getApp().getDocumentManager().getContainers().add(container);
        }
    }

    /**
     * @return Main panel for this application.
     */
    protected abstract JPanel createMainPanel();

    /**
     * @return About box for this application.
     */
    protected abstract PopupBox getAboutBox();

    /**
     * Name of the application.
     *
     * @return String application name.
     */
    public abstract String createApplicationName();

    /**
     * @return Application name.
     */
    public final String getApplicationName() {
        if (applicationName == null) {
            applicationName = createApplicationName();
        }
        return applicationName;
    }

    /**
     * @return Document manager.
     */
    public final DocumentManager getDocumentManager() {
        if (documentManager == null) {
            documentManager = new DocumentManager(PERIOD
                    + getClass().getCanonicalName());
        }
        return documentManager;
    }

    /**
     * @return Menu bar.
     */
    protected final JMenuBar getMenuBar() {
        if (pmenuBar == null) {
            pmenuBar = new MessageMenuBar();
            pmenuBar.add(createFileMenu());
            pmenuBar.add(createHelpMenu());
        }
        return pmenuBar;
    }

    /**
     * @return help menu.
     */
    private JMenu createHelpMenu() {
        JMenu menu = new JMenu("Help");
        menu.setMnemonic(KeyEvent.VK_H);
        menu.setFont(ARIAL_PLAIN_10);

        JMenuItem mi = new JMenuItem(x("About") + SINGLE_SPACE
                + getApplicationName());
        mi.setMnemonic(KeyEvent.VK_A);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                doAbout();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem(x("Java Properties"));
        mi.setMnemonic(KeyEvent.VK_J);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                doJavaProperties();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem(x("System Information"));
        mi.setMnemonic(KeyEvent.VK_J);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                doSystemInfo();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem(x("Log"));
        mi.setMnemonic(KeyEvent.VK_J);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                doShowLog();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        return menu;
    }

    /**
     * @return file menu.
     */
    private JMenu createFileMenu() {
        JMenu menu = new JMenu("File");
        menu.setMnemonic(KeyEvent.VK_F);
        menu.setFont(ARIAL_PLAIN_10);

        JMenuItem mi = new JMenuItem("New");
        mi.setMnemonic(KeyEvent.VK_N);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                getDocumentManager().doNew(AbstractApplication.this);
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem("Open");
        mi.setMnemonic(KeyEvent.VK_O);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                getDocumentManager().doOpen(AbstractApplication.this);
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem("Save");
        mi.setMnemonic(KeyEvent.VK_S);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                getDocumentManager().doSave(AbstractApplication.this);
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem("Save as");
        mi.setMnemonic(KeyEvent.VK_A);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                getDocumentManager().doSaveAs(AbstractApplication.this);
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem("Quit");
        mi.setMnemonic(KeyEvent.VK_Q);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                getFlexFrame().quit();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        return menu;
    }

    /**
     * Override to add menus in between the File and Help menus.
     *
     * @param menuBar
     *            Menu bar.
     */
    protected void addMiddleMenus(final JMenuBar menuBar) {
        // Nothing to do for default behavior.
    }

    /**
     * @return Main panel.
     */
    protected final JPanel getMainPanel() {
        if (mainPanel == null) {
            mainPanel = createMainPanel();
        }
        return mainPanel;
    }

    /**
     * Message menubar.
     */
    class MessageMenuBar extends JMenuBar {

        /**
         * Constructor.
         */
        public MessageMenuBar() {
            super();
            new Thread("painter") {

                @Override
                public void run() {
                    while (Osgw.isActive()) {
                        repaint();
                        try {
                            Thread.sleep(MathUtil.ONETHOUSAND);
                        } catch (InterruptedException e) {
                            log(e);
                        }
                    }
                }
            }
            .start();
        }

        @Override
        public void paint(final Graphics g) {
            super.paint(g);
            if (com.panopset.Util.isPopulated(Alert.getMessage())) {
                paintMessage(g);
            } else {
                paintMemory(g);
            }
        }

        /**
         * Paint available memory.
         *
         * @param g
         *            Graphics.
         */
        private void paintMemory(final Graphics g) {
            long fm = Runtime.getRuntime().freeMemory();
            long tm = Runtime.getRuntime().totalMemory();
            MathUtil.Percentage percentage = MathUtil.percent(fm, tm, 2);
            boolean alert = percentage.getValue().compareTo(
                    new BigDecimal(NINETY)) > 0;
            g.setColor(Color.GRAY);
            if (alert) {
                g.setColor(Color.RED);
            }
            int w = (getSize().width - getLocation().x) / 2;
            int x = getLocation().x + w;
            int y = getLocation().y + 1;
            int h = getSize().height - 2;
            g.fillRect(x, y, w, h);
            g.setColor(Color.LIGHT_GRAY);
            if (alert) {
                g.setColor(Color.BLACK);
            }
            g.fillRect(x + 1, y + 1, w - 2, h - 2);
            g.setFont(ARIAL_PLAIN_12);
            g.setColor(Color.DARK_GRAY);
            if (alert) {
                g.setColor(Color.RED);
            }
            g.drawString("Memory: " + fm + " of " + tm + ", " + percentage
                    + "% used.", w + TEXT_OFFSET_X, h + TEXT_OFFSET_Y);
        }

        /**
         * Paint message.
         *
         * @param g
         *            Graphics.
         */
        private void paintMessage(final Graphics g) {
            g.setColor(Color.GRAY);
            int w = (getSize().width - getLocation().x) / 2;
            int x = getLocation().x + w;
            int y = getLocation().y + 1;
            int h = getSize().height - 2;
            g.fillRect(x, y, w, h);
            g.setColor(Color.BLACK);
            g.fillRect(x + 1, y + 1, w - 2, h - 2);
            g.setFont(ARIAL_PLAIN_12);
            g.setColor(Alert.getColor());
            g.drawString(Alert.getMessage(), w + MathUtil.THREE, h
                    - MathUtil.FOUR);
        }

        /**
         * X text offset is 3.
         */
        private static final int TEXT_OFFSET_X = 3;

        /**
         * Y text offset -4.
         */
        private static final int TEXT_OFFSET_Y = -4;

        /**
         * Serial version id.
         */
        private static final long serialVersionUID = VERSION_SERIAL_NUMBER;
    }

    /**
     * @return Flex frame.
     */
    public final FlexFrame getFlexFrame() {
        if (flexFrame == null) {
            flexFrame = new FlexFrame.Builder().packageIcon(
                    Favicon.getLatest().getPath()).menuBar(getMenuBar()).title(
                    getApplicationName()).defaultSize(getDefaultSize())
                    .applicationInterface(this).construct();
            flexFrame.getFrame().setLayout(new BorderLayout());
            flexFrame.getFrame().add(BorderLayout.CENTER, getMainPanel());
            TPanel sp = new TPanel(new BorderLayout());
            TButton btn = new TButton(x("Clear"));
            btn.setToolTipText(x("Clear message"));
            btn.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(final ActionEvent e) {
                    Alert.clearTopMessage();
                }
            });
            sp.add(BorderLayout.EAST, btn);
            sp.add(BorderLayout.CENTER, Alert.singleton.getMsgLabel());
            flexFrame.getFrame().add(BorderLayout.SOUTH, sp);
            Util.setParentFrame(parentFrame);
        }
        return flexFrame;
    }

    /**
     * About to quit application.
     */
    public abstract void aboutToQuit();

    /**
     * Quit.
     */
    public final void quit() {
        aboutToQuit();
        save();
        getAboutBox().dispose();
        setApp(null);
        Osgw.close();
    }

    /**
     * First time flag.
     */
    private boolean firstTime = true;

    /**
     * Prior to calling this method, make sure that all PanField components are
     * created. If you get a java.util.ConcurrentModificationException when
     * calling this method, it almost certainly means that a PanField subclass
     * was loaded without having first been created.
     *
     * @param value
     *            Usually you would pass true.
     */
    public final void setVisible(final boolean value) {
        if (value && firstTime) {
            getMainPanel();
            load();
            firstTime = false;
        }
        getFlexFrame().setVisible(value);
    }

    /**
     * Do system info.
     */
    public final synchronized void doSystemInfo() {
        new TypicalDialog.Builder(x("System Information")).centerPanel(
                new SysInfoPanel().getGui()).construct().display();
    }

    /**
     * Do Java Properties.
     */
    public final synchronized void doJavaProperties() {
        TypicalDialog.dspdlg("Java System Properties", dump(System
                .getProperties()));
    }

    /**
     * Do show log.
     */
    public final synchronized void doShowLog() {
        TypicalDialog.dspdlg("Log", Util.logWriter.toString());
    }

    /**
     * Show about box.
     */
    public final synchronized void doAbout() {
        getAboutBox().show();
    }

    /**
     * Load from document.
     */
    protected void load() {
        getDocumentManager().load();
    }

    /**
     * Save to document.
     */
    protected void save() {
        getDocumentManager().save();
    }

    /**
     * @return Default size.
     */
    public Dimension getDefaultSize() {
        return new Dimension(MathUtil.EIGHT * MathUtil.ONEHUNDRED, MathUtil.SIX
                * MathUtil.ONEHUNDRED);
    }

    /**
     * @return Document manager properties.
     */
    public final Properties getProperties() {
        return getDocumentManager().getProperties();
    }
}
